using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;

namespace MacomberMap.Common.Database_Interface.CIM.One_Line_Interface
{
    #region Static DDL reading

    /// <summary>
    /// This class provides conversion functions from EMS DDL format into XML
    /// </summary>
    public class MM_OneLine_Parser
    {
        /// <summary>
        /// Read a WebFG file inline, and convert it into an XML file for proper parsing
        /// </summary>
        /// <param name="DDLFileName">The DDL file name</param>
        /// <returns></returns>
        public static XmlDocument ReadDDL(String DDLFileName)
        {            
            //Create our outgoing XML document
            XmlDocument OutDoc = new XmlDocument();
            OutDoc.AppendChild(OutDoc.CreateElement("DDL"));
            OutDoc.DocumentElement.Attributes.Append(OutDoc.CreateAttribute("Filename")).Value = DDLFileName;
            OutDoc.DocumentElement.Attributes.Append(OutDoc.CreateAttribute("Imported")).Value = XmlConvert.ToString(DateTime.Now, XmlDateTimeSerializationMode.Unspecified);

            //Now, parse our incoming document into words
            List<String> OutWords;
            using (StreamReader sRd = new StreamReader(DDLFileName))
                OutWords = ParseWords(sRd.ReadToEnd());

            //Now, go through our words one by one, and handle accordingly.
            XmlElement CurrentElement = OutDoc.DocumentElement;
            int CurDepth = 0;
            bool inIf = false;
            for (int CurWord = 0; CurWord < OutWords.Count; CurWord++)
                ProcessWord(OutWords, ref CurWord, ref CurDepth, ref CurrentElement, ref inIf);

            return OutDoc;
        }

        /// <summary>
        /// Process a word from the stack
        /// </summary>
        /// <param name="OutWords">Our collection of words</param>
        /// <param name="CurWord">Pointer to our current word</param>
        /// <param name="CurDepth">Current parentheses depth</param>
        /// <param name="CurrentElement">Our current element</param>
        /// <param name="inIf"></param>
        private static void ProcessWord(List<string> OutWords, ref int CurWord, ref int CurDepth, ref XmlElement CurrentElement, ref bool inIf)
        {
            //If the word after this one starts with a quote, we have a new attribute with a name. Let's adjust accordingly.
            if (CurWord == OutWords.Count - 1)
                return;
            else if (OutWords[CurWord + 1][0] == '"' && OutWords[CurWord + 2] == "(")
            {
                CurrentElement = CurrentElement.AppendChild(CurrentElement.OwnerDocument.CreateElement(OutWords[CurWord])) as XmlElement;
                AppendAttribute(CurrentElement, "Name", OutWords[CurWord + 1].Trim('"'));
                CurWord += 2;
                CurDepth++;
            }


            //If the word is followed by a parentheses, and then quotes, update accordingly.
            else if (OutWords[CurWord + 1] == "(" && OutWords[CurWord + 3] == ")" && (OutWords[CurWord + 2][0] == '"' || IsNumeric(OutWords[CurWord + 2])))
            {
                AppendAttribute(CurrentElement, OutWords[CurWord], OutWords[CurWord + 2].Trim('"'));
                CurWord += 3;
            }

            //If we have text(number number), handle accordingly
            else if (OutWords[CurWord + 1] == "(" && IsNumeric(OutWords[CurWord + 2]) && IsNumeric(OutWords[CurWord + 3]) && OutWords[CurWord + 4] == ")")
            {
                AppendAttribute(CurrentElement, OutWords[CurWord], OutWords[CurWord + 2] + "," + OutWords[CurWord + 3]);
                CurWord += 4;
            }


            //If we have a justification or format field, adjust adccordingly
            else if (OutWords[CurWord] == "justification" || OutWords[CurWord] == "format" || OutWords[CurWord] == "line_style")
            {
                AppendAttribute(CurrentElement, OutWords[CurWord], OutWords[CurWord + 2].Trim('"'));
                CurWord += 3;
            }

            //If the word has a parenthes below (no name), adjust accordingly
            else if (OutWords[CurWord + 1] == "(" && OutWords[CurWord + 2] == "bitmap")
            {
                AppendAttribute(CurrentElement, OutWords[CurWord], OutWords[CurWord + 4].Trim('"'));
                CurWord += 6;
            }
            else if (OutWords[CurWord + 1] == "(")
            {
                CurrentElement = CurrentElement.AppendChild(CurrentElement.OwnerDocument.CreateElement(OutWords[CurWord])) as XmlElement;
                CurWord++;
                CurDepth++;
            }

            //Handle the "not" keyword by setting the attribute to false
            else if (OutWords[CurWord] == "not")
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute(OutWords[CurWord + 1])).Value = "false";
                CurWord++;
            }

            //Handle closed parentheses
            else if (OutWords[CurWord] == ")")
            {
                CurDepth--;
                CurrentElement = CurrentElement.ParentNode as XmlElement;
                if (inIf)
                {
                    inIf = false;
                    CurrentElement = CurrentElement.ParentNode as XmlElement;
                }
            }

            //Handle the conditional (if, then, etc.) text
            else if (OutWords[CurWord] == "if" || OutWords[CurWord] == "else")
            {
                inIf = true;
                ProcessIf(OutWords, ref CurWord, ref CurrentElement, 0);
            }

            //If the word is followed by a quote, handle it as an attribute
            else if (OutWords[CurWord + 1][0] == '"')
                AppendAttribute(CurrentElement, OutWords[CurWord], OutWords[++CurWord].Trim('"'));

            //If we're starting with a quote, then it's a value
            else if (OutWords[CurWord][0] == '"')
                AppendAttribute(CurrentElement, "Value", OutWords[CurWord].Trim('"'));

            //Otherwise, add an attribute, call it true
            else
                AppendAttribute(CurrentElement, OutWords[CurWord], "true");
        }
        /// <summary>
        /// Parse our incoming text into a series of words
        /// </summary>
        /// <param name="IncomingText"></param>
        /// <returns></returns>
        private static List<String> ParseWords(String IncomingText)
        {
            //Build our list of outgoing words
            List<String> OutWords = new List<string>();
            StringBuilder OutString = new StringBuilder();
            bool inQuote = false;

            //Now, go character by character, and handle accordingly.
            for (int CurPos = 0; CurPos < IncomingText.Length; CurPos++)

                //First, if we have a valid quote character, invert our quote status
                if (IncomingText[CurPos] == '"' && (!inQuote || IncomingText[CurPos - 1] != '\\' || IncomingText[CurPos - 2] != '\\'))

                    if (!(inQuote ^= true))
                        PushCharacter('"', OutWords, OutString, false);
                    else
                        OutString.Append('"');

             //Otherwise, check for parentheses
                else if (IncomingText[CurPos] == '(' && !inQuote)
                    PushCharacter('(', OutWords, OutString, true);

                else if (IncomingText[CurPos] == ')' && !inQuote)
                {
                    PushCharacter(')', OutWords, OutString, true);
                    if (IncomingText[CurPos + 1] == ';')
                        CurPos++;
                }

                //Otherwise, handle spaces
                else if (!inQuote && (IncomingText[CurPos] == ' ' || IncomingText[CurPos] == '\t' || IncomingText[CurPos] == '\n' || IncomingText[CurPos] == '\r'))
                    PushCharacter((Char)0, OutWords, OutString, true);
                else
                    OutString.Append(IncomingText[CurPos]);
            return OutWords;
        }

        /// <summary>
        /// Add or append an attribute to an element
        /// </summary>
        /// <param name="BaseElement">The element receiving the attribute</param>
        /// <param name="AttributeName">The name of the attribute</param>
        /// <param name="AttributeValue">The value of the attribute</param>
        public static void AppendAttribute(XmlElement BaseElement, String AttributeName, String AttributeValue)
        {
            if (BaseElement.HasAttribute(AttributeName))
                BaseElement.Attributes[AttributeName].Value += "," + AttributeValue;
            else
                BaseElement.Attributes.Append(BaseElement.OwnerDocument.CreateAttribute(AttributeName)).Value = AttributeValue;
        }

        /// <summary>
        /// Push a character into our outgoing collection
        /// </summary>
        /// <param name="CharacterToPush">Our character to be pushed</param>
        /// <param name="OutWords">Our outgoing collection of words</param>
        /// <param name="OutString">Our collection string</param>
        /// <param name="SeperateChar">Whether this character should be maintained seperately</param>
        private static void PushCharacter(Char CharacterToPush, List<String> OutWords, StringBuilder OutString, bool SeperateChar)
        {
            //If this is a seperate character, first flush out the old text
            if (SeperateChar)
            {
                String OutStr = OutString.ToString().Trim(' ', '\t', '\r', '\n');
                if (OutStr.Length > 0 && OutStr != "localize" && OutStr != "ylocked" && OutStr != "xlocked")
                    OutWords.Add(OutStr);
                OutString.Remove(0, OutString.Length);
                if (CharacterToPush != (char)0)
                    OutWords.Add(new string(CharacterToPush, 1));
            }
            else
            {
                OutString.Append(CharacterToPush);
                String OutStr = OutString.ToString().Trim(' ', '\t', '\r', '\n');
                if (OutStr.Length > 0 && OutStr != "localize")
                    OutWords.Add(OutStr);
                OutString.Remove(0, OutString.Length);
            }
        }

        /// <summary>
        /// Determine if a string may pass as a numeric number
        /// </summary>
        /// <param name="InString">The string to check</param>
        /// <returns></returns>
        private static bool IsNumeric(string InString)
        {
            foreach (Char c in InString)
                if (!Char.IsNumber(c) && c != '.' && c != '-')
                    return false;
            return true;
        }

        /// <summary>
        /// Handle an if/then/else block
        /// </summary>
        /// <param name="OutWords">Our collection of outgoing words</param>
        /// <param name="CurWord">Our current word</param>
        /// <param name="CurrentElement">Our current XML element</param>
        /// <param name="ConditionalDepth">The conditional depth (0 for the first if, etc... subsequent OR and And statements increase this)</param>
        private static void ProcessIf(List<string> OutWords, ref int CurWord, ref XmlElement CurrentElement, int ConditionalDepth)
        {
            //If our text is 'else if', handle accordingly, otherwise create based on our next word                        
            bool HasPredicate = false;
            if (OutWords[CurWord] == "else" && OutWords[CurWord + 1] == "if")
            {
                CurrentElement = CurrentElement.AppendChild(CurrentElement.OwnerDocument.CreateElement("else_if")) as XmlElement;
                CurWord += 2;
                HasPredicate = true;
            }
            else if (OutWords[CurWord] == "if")
            {
                CurrentElement = CurrentElement.AppendChild(CurrentElement.OwnerDocument.CreateElement(OutWords[CurWord++])) as XmlElement;
                HasPredicate = true;
            }
            else if (OutWords[CurWord] == "else")
            {
                CurrentElement = CurrentElement.AppendChild(CurrentElement.OwnerDocument.CreateElement(OutWords[CurWord])) as XmlElement;
                HasPredicate = false;
            }
            else if (OutWords[CurWord] == "and" || OutWords[CurWord] == "or")
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Merge" + ConditionalDepth.ToString())).Value = OutWords[CurWord++];
                HasPredicate = true;
            }

            //Now, handle the subject text
            if (HasPredicate && (OutWords[CurWord] == "field" || OutWords[CurWord] == "proxy"))
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Subject" + ConditionalDepth.ToString())).Value = OutWords[CurWord + 2].Trim('"');
                CurWord += 4;
            }
            else if (HasPredicate && OutWords[CurWord] == "indirect")
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Subject" + ConditionalDepth.ToString())).Value = HandleIndirect(OutWords, ref CurWord);
                CurWord++;
            }

            //Now, determine our conditional and other text     
            bool WasSimple = !HasPredicate;
            if (HasPredicate && (OutWords[CurWord] == "true" || OutWords[CurWord] == "false"))
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Condition" + ConditionalDepth.ToString())).Value = OutWords[CurWord].Trim('"');
                WasSimple = true;
            }
            else if (HasPredicate && OutWords[CurWord] == "not")
            {
                WasSimple = false;
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Condition" + ConditionalDepth.ToString())).Value = OutWords[CurWord++] + " " + OutWords[CurWord];
            }

            //Now, handle our target
            if (!WasSimple && (OutWords[CurWord + 1] == "proxy" || OutWords[CurWord + 1] == "field"))
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Target" + ConditionalDepth.ToString())).Value = OutWords[CurWord + 2].Trim('"');
                CurWord += 4;
            }
            else if (!WasSimple && (OutWords[CurWord + 1] == "indirect"))
            {
                Console.Write("?");
            }
            else if (!WasSimple)
            {
                CurrentElement.Attributes.Append(CurrentElement.OwnerDocument.CreateAttribute("Target" + ConditionalDepth.ToString())).Value = OutWords[CurWord + 1].Trim('"');
                CurWord++;
            }


            //Now, if our next word is 'or' or 'and', run that through too.
            if (OutWords[CurWord + 1] == "or" || OutWords[CurWord + 1] == "and")
            {
                CurWord++;
                ProcessIf(OutWords, ref CurWord, ref CurrentElement, ConditionalDepth + 1);
            }
        }


        /// <summary>
        /// Integrate an indirect pointer into our format
        /// </summary>
        /// <param name="OutWords">Our collection of words</param>
        /// <param name="CurWord">Our current word</param>
        /// <returns></returns>
        private static string HandleIndirect(List<string> OutWords, ref int CurWord)
        {
            Stack<String> Pointers = new Stack<string>();
            StringBuilder OutString = new StringBuilder();
            CurWord += 2;
            while (OutWords[CurWord] == "field")
            {
                Pointers.Push(OutWords[CurWord + 2].Trim('"'));
                CurWord += 4;
            }

            while (Pointers.Count > 0)
                OutString.Append((OutString.Length > 0 ? ">" : "") + Pointers.Pop().Replace("$", "__"));
            return OutString.ToString();
        }
    #endregion
    }
}
